package com.ikingtech.platform.service.datasource.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.context.event.SystemInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.datasource.api.DatasourceApi;
import com.ikingtech.framework.sdk.datasource.model.DatasourceDTO;
import com.ikingtech.framework.sdk.datasource.model.DatasourceQueryParamDTO;
import com.ikingtech.framework.sdk.datasource.model.DatasourceTableDTO;
import com.ikingtech.framework.sdk.datasource.model.DatasourceTableFieldDTO;
import com.ikingtech.framework.sdk.datasource.model.enums.DatasourceSchemaTypeEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.datasource.entity.DatasourceDO;
import com.ikingtech.platform.service.datasource.exception.DatasourceExceptionInfo;
import com.ikingtech.platform.service.datasource.service.DatasourceExecutor;
import com.ikingtech.platform.service.datasource.service.DatasourceRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/datasource", name = "系统管理-数据源管理", description = "系统管理-数据源管理")
public class DatasourceController implements DatasourceApi {

    private final DatasourceRepository repo;

    private final DataSourceProperties dataSourceProperties;

    private final DatasourceExecutor executor;

    /**
     * 添加数据源
     * @param datasource 数据源信息
     * @return 返回添加成功后的数据源ID
     */
    @Override
    @OperationLog(value = "新增数据源", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(DatasourceDTO datasource) {
        // 将传入的数据源信息转换为实体类对象
        DatasourceDO entity = Tools.Bean.copy(datasource, DatasourceDO.class);
        // 生成数据源ID
        entity.setId(Tools.Id.uuid());
        // 设置租户代码
        entity.setTenantCode(Me.tenantCode());
        // 保存数据源实体到数据库
        this.repo.save(entity);
        // 返回添加成功后的数据源ID
        return R.ok(entity.getId());
    }

    /**
     * 删除数据源
     * @param id 数据源ID
     * @return 删除结果
     */
    @Override
    @OperationLog(value = "删除数据源")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        // 删除数据源
        this.repo.removeById(id);
        return R.ok();
    }

    /**
     * 更新数据源
     * @param datasource 数据源信息
     * @return 返回更新结果
     */
    @Override
    @OperationLog(value = "更新数据源")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(DatasourceDTO datasource) {
        // 检查数据源是否存在
        if (!this.repo.exists(Wrappers.<DatasourceDO>lambdaQuery().eq(DatasourceDO::getId, datasource.getId()))) {
            throw new FrameworkException(DatasourceExceptionInfo.DATASOURCE_NOT_FOUND);
        }
        // 将DTO转换为DO对象
        DatasourceDO entity = Tools.Bean.copy(datasource, DatasourceDO.class);
        // 更新数据源
        this.repo.updateById(entity);
        return R.ok();
    }

    /**
     * 分页查询数据源
     *
     * @param queryParam 查询参数
     * @return 分页结果
     */
    @Override
    public R<List<DatasourceDTO>> page(DatasourceQueryParamDTO queryParam) {
        // 调用分页查询方法
        return R.ok(PageResult.build(this.repo.page(new Page<>(queryParam.getPage(), queryParam.getRows()), DatasourceRepository.createWrapper(queryParam, Me.tenantCode()))).convert(this::modelConvert));
    }

    /**
     * 获取所有数据源
     *
     * @return 返回数据源列表
     */
    @Override
    public R<List<DatasourceDTO>> all() {
        return R.ok(this.modelConvert(this.repo.list(Wrappers.<DatasourceDO>lambdaQuery().eq(Tools.Str.isNotBlank(Me.tenantCode()), DatasourceDO::getTenantCode, Me.tenantCode()))));
    }

    /**
     * 获取指定id的数据源详情
     * @param id 数据源id
     * @return 数据源详情
     */
    @Override
    public R<DatasourceDTO> detail(String id) {
        // 根据id获取数据源实体
        DatasourceDO entity = this.repo.getById(id);
        // 如果实体为空，则抛出数据源未找到异常
        if (null == entity) {
            throw new FrameworkException(DatasourceExceptionInfo.DATASOURCE_NOT_FOUND);
        }
        // 将实体转换为数据源DTO并返回
        return R.ok(this.modelConvert(entity));
    }

    /**
     * 获取数据源的模式列表
     * @param datasource 数据源信息
     * @return 模式列表
     */
    @Override
    public R<List<String>> listSchema(DatasourceDTO datasource) {
        // 执行数据库操作
        List<Map<String, Object>> schemas = this.executor.execute(datasource.getType(), datasource.getType().jdbcUrl(datasource.getIp(), datasource.getPort()), datasource.getUsername(), datasource.getPassword(), "select * from information_schema.schemata");
        // 将结果转换为模式名称列表
        return R.ok(Tools.Coll.convertList(schemas, schema -> String.valueOf(schema.get("SCHEMA_NAME"))));
    }

    /**
     * 根据id获取表信息
     * @param id 数据源id
     * @return 表信息列表
     */
    @Override
    public R<List<DatasourceTableDTO>> listTableById(String id) {
        // 根据id获取数据源实体
        DatasourceDO entity = this.repo.getById(id);
        if (null == entity) {
            throw new FrameworkException(DatasourceExceptionInfo.DATASOURCE_NOT_FOUND);
        }
        // 查询数据源所属schema下的所有表
        List<Map<String, Object>> schemaTables = this.executor.execute(entity, Tools.Str.format("select * from information_schema.tables where table_schema = '{}'", entity.getSchemaName()));
        // 查询数据源所属schema下的所有字段
        List<Map<String, Object>> schemaTableFields = this.executor.execute(entity, Tools.Str.format("select * from information_schema.columns where table_schema = '{}'", entity.getSchemaName()));

        // 将查询结果转换为表信息列表
        return R.ok(Tools.Coll.convertList(schemaTables, schemaTable -> {
            DatasourceTableDTO table = new DatasourceTableDTO();
            String tableName = String.valueOf(schemaTable.get("TABLE_NAME"));
            table.setName(tableName);
            table.setDescription(String.valueOf(schemaTable.get("TABLE_COMMENT")));
            table.setFields(Tools.Coll.convertList(schemaTableFields, schemaTableField -> tableName.equals(String.valueOf(schemaTableField.get("TABLE_NAME"))), schemaTableField -> {
                DatasourceTableFieldDTO field = new DatasourceTableFieldDTO();
                field.setName(String.valueOf(schemaTableField.get("COLUMN_NAME")));
                field.setType(String.valueOf(schemaTableField.get("DATA_TYPE")));
                Long fieldLength = (Long) schemaTableField.get("CHARACTER_MAXIMUM_LENGTH");
                field.setLength(null == fieldLength ? null : fieldLength.intValue());
                field.setNullable("YES".equals(String.valueOf(schemaTableField.get("IS_NULLABLE"))));
                field.setPrimaryKey("PRI".equals(String.valueOf(schemaTableField.get("COLUMN_KEY"))));
                field.setComment(String.valueOf(schemaTableField.get("COLUMN_COMMENT")));
                return field;
            }));
            return table;
        }));
    }

    /**
     * 注册系统初始化事件监听器
     */
    @EventListener(SystemInitEvent.class)
    public void systemInitEventListener() {
        // 获取默认数据源
        DatasourceDO entity = this.repo.getOne(Wrappers.<DatasourceDO>lambdaQuery().eq(DatasourceDO::getDefaultDatasource, true));
        if (null == entity) {
            entity = new DatasourceDO();
            entity.setId(Tools.Id.uuid());
        }
        entity.setName("默认数据源");
        entity.setUsername(this.dataSourceProperties.getUsername());
        entity.setPassword(this.dataSourceProperties.getPassword());
        entity.setType(DatasourceSchemaTypeEnum.valueOfDriverClassName(this.dataSourceProperties.getDriverClassName()).name());
        String host = DatasourceSchemaTypeEnum.parseHostByJdbcUrl(entity.getType(), this.dataSourceProperties.getUrl());
        entity.setIp(Tools.Str.split(host, ":").get(0));
        entity.setPort(Tools.Str.split(host, ":").get(1));
        entity.setJdbcUrl(this.dataSourceProperties.getUrl());
        entity.setType(DatasourceSchemaTypeEnum.MYSQL.name());
        entity.setDefaultDatasource(true);
        entity.setSchemaName("iking_framework");
        this.repo.saveOrUpdate(entity);
    }

    /**
     * 将实体列表转换为DTO列表
     *
     * @param entities 实体列表
     * @return DTO列表
     */
    private List<DatasourceDTO> modelConvert(List<DatasourceDO> entities) {
        return Tools.Coll.convertList(entities, this::modelConvert);
    }


    /**
     * 将实体对象转换为DTO对象
     * @param entity 实体对象
     * @return 转换后的DTO对象
     */
    private DatasourceDTO modelConvert(DatasourceDO entity) {
        // 使用工具类将实体对象转换为DTO对象
        DatasourceDTO datasource = Tools.Bean.copy(entity, DatasourceDTO.class);

        // 如果类型不为空，则将类型名称设置为类型描述
        if (null != datasource.getType()) {
            datasource.setTypeName(datasource.getType().description);
        }

        return datasource;
    }

}
